#include <sys/statfs.h>
#include "recordServer.h"
#include "Util/util.h"
#include "Util/File.h"
#include "ProtocolApi.h"
#include "StreamApi.h"
#include "ConfigApi.h"
#include "MediaFileDbManager.h"
#include "MediaApi.h"

using namespace ConfigApi;
using namespace Stream;
using namespace Protocol;
using namespace Media;

// extern "C" int MEDIA_RecorderStart(void);
// extern "C" int MEDIA_RecorderStop(void);
// extern "C" int DEV_GetRecordStatus(void);

const string stream_app_name = "live";
recordServer *recordServer::s_recordServer = nullptr;
using namespace Fdt;

bool getRoomId(std::string& id){
	Json::Value kv;
    int result = 0;
	std::string config_path = "Room";
    bool ret = IConfigInterface::GetInstance()->Get(config_path, kv, result);
	if(ret && kv.isMember("Id")){
        id = kv["Id"].asString();
		return true;
    }

	errorf("fail get Room Id");
	return false;
}


bool getStreamPushUrl(std::string& url, const string& channel = "mainstream"){
	Json::Value kv;
    int result = 0;
	std::string config_path = "URL";
    bool ret = IConfigInterface::GetInstance()->Get(config_path, kv, result);

    if(ret && kv.isMember("MediaServerUrl")){
        url = kv["MediaServerUrl"].asString();
		string id;
		if(getRoomId(id)){
			url = "rtsp://" + url + "/record_" + id  + "/" + channel + "?room_id=" + id;
			return true;
		}
    }
	errorf("fail get StreamPushUrl ret %d\n", ret);
	return false;
}


recordServer::recordServer(){
    RegistRPC();
}

void recordServer::RegistRPC(){
    RPCMethodMap method_map;
	method_map.emplace("Start", CRPCFuntion::CreateRpc([this](const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
		return recordStart(request, inMap, response, outMap);
	}));
	
    method_map.emplace("Stop", CRPCFuntion::CreateRpc([this](const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
		return recordStop(request, inMap, response, outMap);
	}));

    method_map.emplace("List", CRPCFuntion::CreateRpc([this](const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
		return recordList(request, inMap, response, outMap);
	}));

    method_map.emplace("GetSd", CRPCFuntion::CreateRpc([this](const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
		return recordGetSd(request, inMap, response, outMap);
	}));

    method_map.emplace("FormatSd", CRPCFuntion::CreateRpc([this](const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
		return recordFormatSd(request, inMap, response, outMap);
	}));

    method_map.emplace("Status", CRPCFuntion::CreateRpc([this](const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
		return recordStatus(request, inMap, response, outMap);
	}));

	IProtocolInterface::GetInstance()->RegistProtocol("Record", method_map);

    IStreamInterface::GetInstance()->SetMp4RecordDoneCallBack([](const std::string& file_path, 
		const std::string& channel, uint32_t file_size, time_t start_time, time_t stop_time){
            string room_id;
            if(getRoomId(room_id)){
				tracef("mp4 record file done %s, %d, %d", file_path.c_str(), start_time, stop_time);
                auto media_file = MediaFile{0, room_id, file_path, channel, start_time,
                    stop_time, file_size, false};
                MediaFileDbManager::Instance()->AddFileEntry(media_file);
            }
        });

	_thread.start([this](Fdt::Thread *thread){this->ThreadProc(thread);});
}


bool recordServer::recordStart(const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
	tracef("json------------------------>%s", request.toStyledString().c_str());
    std::lock_guard<std::recursive_mutex> guard(m_mutex);

	if(m_is_recording){
		response["data"]["Param"]["Result"] = false;
		return false;
	}
	bool recordAll = false;
	if(request.isMember("Param")){
        const Json::Value &param = request["Param"];
		Json::getJsonBool(param, "RecordAll", recordAll);
    }

	bool record = false;
	if(mountFlag == 1){
		record = true;
	}

    IStreamInterface::GetInstance()->StartRecord(stream_app_name, "mainstream");

    string push_url;
    m_push_urls.clear();
    if(getStreamPushUrl(push_url, "mainstream")){
        m_push_urls.push_back(push_url);
        IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "mainstream");
	}

    if(recordAll){
		//camera
        if(getStreamPushUrl(push_url, "substream4")){
            m_push_urls.push_back(push_url);
            IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "substream4");
            IStreamInterface::GetInstance()->StartRecord(stream_app_name, "substream4");
        }

		//camera
        if(getStreamPushUrl(push_url, "substream2")){
            m_push_urls.push_back(push_url);
            IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "substream2");
            IStreamInterface::GetInstance()->StartRecord(stream_app_name, "substream2");
        }

		//camera
        if(getStreamPushUrl(push_url, "substream9")){
            m_push_urls.push_back(push_url);
            IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "substream9");
            IStreamInterface::GetInstance()->StartRecord(stream_app_name, "substream9");
        }

		//camera
        if(getStreamPushUrl(push_url, "substream10")){
            m_push_urls.push_back(push_url);
            IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "substream10");
            IStreamInterface::GetInstance()->StartRecord(stream_app_name, "substream10");
        }

		//hdmi + audio
		if(getStreamPushUrl(push_url, "substream7")){
            m_push_urls.push_back(push_url);
            IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "substream7");
            IStreamInterface::GetInstance()->StartRecord(stream_app_name, "substream7");
        }

		//audio only
		if(getStreamPushUrl(push_url, "substream1")){
            m_push_urls.push_back(push_url);
            IStreamInterface::GetInstance()->StartPushStream(push_url, stream_app_name, "substream1");
            IStreamInterface::GetInstance()->StartRecord(stream_app_name, "substream1");
        }

    }
	response["data"]["Param"]["Result"] = true;
	m_is_recording = true;
	return true;
}

bool recordServer::recordStop(const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
	tracef("json------------------------>%s", request.toStyledString().c_str());
    std::lock_guard<std::recursive_mutex> guard(m_mutex);

	response["data"]["Param"]["Result"] = true;
    if(m_is_recording){
        IStreamInterface::GetInstance()->StopRecord();
        for(auto url:m_push_urls){
            IStreamInterface::GetInstance()->StopPushStream(url);
        }
    }

    m_is_recording = false;
	return true;
}

bool recordServer::recordList(const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
	std::lock_guard<std::recursive_mutex> guard(m_mutex);

	std::string timeStr;
	if(!Json::getJsonString(request, "Param.Time", timeStr)){
		response["data"]["Param"]["Result"] = false;
		return false;
	}

	std::list<std::string> filelist;
	std::string dir = mountDir;
	dir += "/RecordFile/mainstream";

    File::scanDir(dir + '/' + timeStr, [&filelist](const std::string &path, bool isDir){ 
    	filelist.push_back(path);
    	return true;
	});

	for(auto &filename : filelist){
		if (strstr(filename.c_str(), ".mp4"))
		{
			auto pos = filename.find_last_of('/');
			response["data"]["Param"]["FileList"].append(filename.substr(pos + 1));
		}
	}

	response["data"]["Param"]["Result"] = true;
	return true;
}

bool recordServer::recordStatus(const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
	std::lock_guard<std::recursive_mutex> guard(m_mutex);

    response["data"]["Param"]["Enable"] = m_is_recording;
	response["data"]["Param"]["Result"] = true;
	return true;
}

bool recordServer::recordGetSd(const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
	tracef("===============================");
	FILE * fp;
	char *line = NULL;
	size_t len = 0;
	ssize_t read;
	fp = fopen("/proc/mounts", "r");
	if (fp == NULL)
	{
		return false;
	}
	while ((read = getline(&line, &len, fp)) != -1)
	{
		if (strstr(line, mountDir.c_str()))
		{
			struct statfs diskInfo;
			statfs(mountDir.c_str(), &diskInfo);	
			long long blocksize = diskInfo.f_bsize;                   
			long long totalsize = blocksize * diskInfo.f_blocks;     
			long long availableDisk = diskInfo.f_bavail * blocksize;

			response["data"]["Param"]["Path"] = mountDir;
			response["data"]["Param"]["Capacity"] = totalsize/1024;
			response["data"]["Param"]["Free"] = availableDisk/1024;
			response["data"]["Param"]["Status"] = "Mount";
			response["data"]["Param"]["Result"] = true;
			if (line)
			{
				free(line);
			}
			fclose(fp);
			return true;
		}
	}
	if (line)
	{
		free(line);
	}
	fclose(fp);

	if (needFormatCard)
	{
		response["data"]["Param"]["Status"] = "Format";
	}
	else
	{
		response["data"]["Param"]["Status"] = "None";
	}
	response["data"]["Param"]["Result"] = true;
	return true;
}

bool recordServer::recordFormatSd(const Json::Value &request, const BufferMap::Ptr &inMap, Json::Value &response, BufferMap::Ptr &outMap){
	std::lock_guard<std::recursive_mutex> guard(m_mutex);

    IStreamInterface::GetInstance()->StopRecord();
    MediaFileDbManager::Instance()->Reset();
	m_is_recording = false;

	if (!pStorage)
	{
		response["data"]["Param"]["Result"] = false;
		return false;
	}

	pStorage->devUmount(pStorage, (char *)mountDir.c_str());
	mountFlag = 0;
	if (partNum <= 0)
	{
		pStorage->addPartition(pStorage, stStorageInfo.name, 1, stStorageInfo.capacity);
		tracef("Add partition for %s \n", stStorageInfo.name);
	}
	pStorage->formatPartition(pStorage, stStoragePartitionInfo.name, "vfat");
	tracef("Format Partition %s to vfat\n", stStoragePartitionInfo.name);

	pStorage->devMount(pStorage, stStoragePartitionInfo.name, (char *)mountDir.c_str(), "vfat"/*stStoragePartitionInfo.fs*/);
	tracef("mount Partition %s to %s\n", stStoragePartitionInfo.name, mountDir.c_str());
	needFormatCard = 0;
	partNum = 1;
	mountFlag = 1;
	response["data"]["Param"]["Result"] = true;
	return true;
}

void recordServer::ThreadProc(Fdt::Thread *thread)
{
	int ret = -1;
	int max = 0;
	int partMax = 0;
    StorageDesc storageDesc;
    
	tracef("recordServer Thread tid %d", thread->tid());
    thread->setPriority(Fdt::PRIORITY_NORMAL, Fdt::POLICY_RR);

    while (thread->running())
    {
		max = 1;
		partMax = 1;
		
		ret = getStorageList(&stStorageInfo, &max);
		if (!ret)
		{
			storageDesc.name = stStorageInfo.name;
			ret = createStorage(&storageDesc, &pStorage);		
			if (ret < 0)
			{
				tracef("createStorage fail! umount %s\n", mountDir.c_str());
				if (1 == mountFlag)
				{
					IMediaInterface::GetInstance()->StopReadUsbCard();
					int ret = pStorage->devUmount(pStorage, (char *)mountDir.c_str());
					mountFlag = 0;
				}
				needFormatCard = 0;
                if(mountFlag == 0){
                    MediaFileDbManager::Instance()->Reset();
                }
				IMediaInterface::GetInstance()->CloseDisplayWarnSD();
				continue;
			}
		
			pStorage->getPartitionList(pStorage, &stStoragePartitionInfo, &partMax);
			if (partMax > 0)
			{
				ret = pStorage->devMount(pStorage, stStoragePartitionInfo.name, (char *)mountDir.c_str(), stStoragePartitionInfo.fs);
				if (ret < 0)
				{
					needFormatCard = 1;
					tracef("get %s, Need format tf card!\n", stStoragePartitionInfo.name);
					IMediaInterface::GetInstance()->DisplayWarnSD();
				}
				else
				{
					needFormatCard = 0;
					mountFlag = 1;
                    thread->sleep(5000);
                    MediaFileDbManager::Instance()->Init("/mnt/sd");
					IMediaInterface::GetInstance()->CloseDisplayWarnSD();
				}
                
				tracef("name:%s, type:%s\n", stStoragePartitionInfo.name, stStoragePartitionInfo.fs);
			}
			else
			{
				needFormatCard = 1;
				tracef("Need format tf card!\n");
			}

			partNum = partMax;
            if(mountFlag == 0){
                MediaFileDbManager::Instance()->Reset();
            }
		}
        thread->sleep(500);
    }
}